﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using MvvmFoundation.Wpf;
using Zuehlke.Blueprint.Quickstart.Architecture.Domain.Services.Interfaces;
using Zuehlke.Blueprint.Quickstart.Architecture.Presentation.Services.Interfaces.BackgroundTasks;
using Zuehlke.Blueprint.Quickstart.Architecture.Presentation.Services.Interfaces.Customers;

namespace Zuehlke.Blueprint.Quickstart.Architecture.Presentation.Services.Customers
{
    /// <summary>
    /// See <see cref="ICustomerService"/>.
    /// </summary>
    public class CustomerService : ICustomerService
    {
        #region Private Members

        private readonly ICustomerPersistencyService _customerPersistencyService;
        private readonly IBackgroundTasksService _backgroundTasksService;
        
        private readonly RelayCommand _startLoadingCustomersCommand;
        private readonly RelayCommand _cancelLoadingCustomersCommand;
        
        private readonly CustomerServiceInfoImpl _customerServiceInfo = new CustomerServiceInfoImpl();

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="CustomerService"/> class.
        /// </summary>
        public CustomerService(
            ICustomerPersistencyService customerPersistencyService,
            IBackgroundTasksService backgroundTasksService)
        {
            _customerPersistencyService = customerPersistencyService;
            _backgroundTasksService = backgroundTasksService;

            _startLoadingCustomersCommand = new RelayCommand(StartLoadingCustomers, CanStartLoadingCustomers);
            _cancelLoadingCustomersCommand = new RelayCommand(CancelLoadingCustomers, CanCancelLoadingCustomers);
        }

        #endregion

        #region ICustomerService Members

        /// <summary>
        /// See <see cref="ICustomerService.StartLoadingCustomersCommand"/>
        /// </summary>
        public ICommand StartLoadingCustomersCommand
        {
            get { return _startLoadingCustomersCommand; }
        }

        /// <summary>
        /// See <see cref="ICustomerService.CancelLoadingCustomersCommand"/>
        /// </summary>
        public ICommand CancelLoadingCustomersCommand
        {
            get { return _cancelLoadingCustomersCommand; }
        }

        /// <summary>
        /// See <see cref="ICustomerService.Info"/>
        /// </summary>
        public ICustomerServiceInfo Info
        {
            get { return _customerServiceInfo; }
        }

        #endregion

        #region Private Helpers

        #region Command Methods

        private bool CanStartLoadingCustomers()
        {
            return (_customerServiceInfo.BackgroundTask == null);
        }
        
        private void StartLoadingCustomers()
        {
            if(!CanStartLoadingCustomers())
            {
                return;
            }

            _customerServiceInfo.GetCustomers().Clear();
            
            _customerServiceInfo.BackgroundTask = _backgroundTasksService.PerformInBackground(GetCustomers, GetCustomersFinished, null);
            UpdateCommands();
        }

        private bool CanCancelLoadingCustomers()
        {
            return (_customerServiceInfo.BackgroundTask != null);
        }

        private void CancelLoadingCustomers()
        {
            if (!CanCancelLoadingCustomers())
            {
                return;
            }

            _customerServiceInfo.BackgroundTask.RequestCancel();
            UpdateCommands();
        }

        private void UpdateCommands()
        {
            _startLoadingCustomersCommand.RaiseCanExecuteChanged();
            _cancelLoadingCustomersCommand.RaiseCanExecuteChanged();
        }

        #endregion

        private IEnumerable<Customer> GetCustomers(IBackgroundTask backgroundTask, object taskData)
        {
            double progress = 0;
            backgroundTask.ReportProgress((int)progress);

            var customers = new List<Customer>();
            
            var numberOfCustomers = _customerPersistencyService.GetCustomersCount();
            double progressStep = 100d / numberOfCustomers;

            for (var i = 0; i < numberOfCustomers; i++)
            {
                if (backgroundTask.IsCancelled)
                {
                    return customers;
                }

                var readonlyCustomer = _customerPersistencyService.LoadCustomer(i);

                if (backgroundTask.IsCancelled)
                {
                    return customers;
                }

                var imageSource = ImageToBitmapImage(readonlyCustomer.Photo);
                var customer = new Customer(readonlyCustomer.FirstName, readonlyCustomer.LastName, imageSource, readonlyCustomer.Title,
                    readonlyCustomer.PhoneWork, readonlyCustomer.MailAddressWork);
                customers.Add(customer);
                progress += progressStep;
                backgroundTask.ReportProgress((int)Math.Round(progress));
            }

            backgroundTask.ReportProgress(100);
            return customers;
        }

        private static ImageSource ImageToBitmapImage(Image image)
        {
            var dispatcher = (Application.Current != null) ? Application.Current.Dispatcher : null;
            if(image == null
                || dispatcher == null)
            {
                return null;
            }

            // ImageSource has to be created in the GUI thread for later data binding!
            var imageSource = dispatcher.Invoke(new Func<ImageSource>(() =>
                                             {
                                                 using (var memoryStream = new MemoryStream())
                                                 {
                                                     image.Save(memoryStream, ImageFormat.Jpeg);
                                                     memoryStream.Position = 0;
                                                     var bitmapImage = new BitmapImage();
                                                     bitmapImage.BeginInit();
                                                     bitmapImage.StreamSource = memoryStream;
                                                     bitmapImage.EndInit();
                                                     memoryStream.Close();

                                                     return bitmapImage;
                                                 }
                                             }), DispatcherPriority.Normal);

            return (ImageSource)imageSource;
        }

        private void GetCustomersFinished(IFinishedBackgroundTask<IEnumerable<Customer>> result)
        {
            if (result.Exception != null)
            {
                return;
            }

            foreach (var customer in result.Result)
            {
                _customerServiceInfo.GetCustomers().Add(customer);
            }

            _customerServiceInfo.BackgroundTask = null;
            UpdateCommands();
        }

        #endregion

        #region Nested Classes

        private class CustomerServiceInfoImpl : ObservableObject, ICustomerServiceInfo
        {
            #region Private Members

            private readonly ListCollectionView _customersCollectionView;
            private readonly ObservableCollection<ICustomer> _customers = new ObservableCollection<ICustomer>(); 

            private IBackgroundTask _backgroundTask;

            #endregion

            #region Constructor

            public CustomerServiceInfoImpl()
            {
                _customersCollectionView = new ListCollectionView(_customers);
            }

            #endregion

            #region ICustomerServiceInfo Members

            public IBackgroundTask BackgroundTask
            {
                get { return _backgroundTask; }
                set
                {
                    _backgroundTask = value;
                    RaisePropertyChanged("BackgroundTask");
                }
            }
            
            public ICollectionView Customers
            {
                get { return _customersCollectionView; }
            }

            #endregion

            #region Public Functionality

            public ObservableCollection<ICustomer> GetCustomers()
            {
                return _customers;
            }

            #endregion
        }

        private class Customer : ObservableObject, ICustomer
        {
            #region Private Members

            private string _firstName;
            private string _lastName;
            private ImageSource _photo;
            private string _title;
            private string _phoneWork;
            private string _mailAddressWork;

            #endregion

            #region Constructor

            public Customer(string firstName, string lastName, ImageSource photo, string title, string phoneWork, string mailAddressWork)
            {
                _firstName = firstName;
                _lastName = lastName;
                _photo = photo;
                _title = title;
                _phoneWork = phoneWork;
                _mailAddressWork = mailAddressWork;
            }

            #endregion

            #region ICustomer Members

            public string FirstName
            {
                get { return _firstName; }
                set
                {
                    _firstName = value;
                    RaisePropertyChanged("FirstName");
                }
            }

            public string LastName
            {
                get { return _lastName; }
                set
                {
                    _lastName = value;
                    RaisePropertyChanged("LastName");
                }
            }

            public ImageSource Photo
            {
                get { return _photo; }
            }

            public string Title
            {
                get { return _title; }
                set
                {
                    _title = value;
                    RaisePropertyChanged("Title");
                }
            }

            public string PhoneWork
            {
                get { return _phoneWork; }
                set
                {
                    _phoneWork = value;
                    RaisePropertyChanged("PhoneWork");
                }
            }

            public string MailAddressWork
            {
                get { return _mailAddressWork; }
                set
                {
                    _mailAddressWork = value;
                    RaisePropertyChanged("MailAddressWork");
                }
            }

            #endregion
        }

        #endregion
    }
}
